﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Tetris.Game.Common
{
    public static class CloneFactory
    {
        private static readonly Dictionary<Type, Delegate> cloneCache = new Dictionary<Type, Delegate>();

        public static T DeepClone<T>(this T source)
        {
            if (!cloneCache.TryGetValue(typeof(T), out var cachedFunc))
            {
                var cloneFunc = BuildCloneFunction<T>();
                cloneCache[typeof(T)] = cloneFunc;
                return (T)cloneFunc.DynamicInvoke(source);
            }

            return ((Func<T, T>)cachedFunc)(source);
        }

        private static Func<T, T> BuildCloneFunction<T>()
        {
            var type = typeof(T);
            var parameter = Expression.Parameter(type, "source");
            var memberBindings = new List<MemberBinding>();

            foreach (var property in type.GetProperties())
            {
                if (property.CanRead && property.CanWrite)
                {
                    var propertyAccess = Expression.Property(parameter, property);
                    var propertyBinding = Expression.Bind(property, propertyAccess);
                    memberBindings.Add(propertyBinding);
                }
            }

            var memberInit = Expression.MemberInit(Expression.New(type), memberBindings);
            var lambda = Expression.Lambda<Func<T, T>>(memberInit, parameter);
            return lambda.Compile();
        }
    }
}
